package com.wxc.config.shiro;


import com.wxc.contants.Constant;
import com.wxc.utils.token.JwtTokenUtil;
import com.wxc.vo.REnum;
import com.wxc.vo.Resp;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author wxc
 * @date 2021年10月13日 15:11
 * 过滤器中抛出的异常无法被异常处理类捕获
 */
@Component
@Slf4j
public class OAuth2Filter extends AuthenticatingFilter {

    /**
     * 拦截请求之后，用于把令牌字符串封装成令牌对象
     * executeLogin里调用
     */
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) servletRequest);

        if (StringUtils.isEmpty(token)) {
            return null;
        }

        return new OAuth2Token(token);
    }

    /**
     * @param request
     * @param response
     * @param mappedValue
     * @return boolean
     * 如果返回true那么就转到下一个过滤器
     * 如果返回false那么会走到onAccessDenied进行处理
     * @date 2021/10/24 18:33
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request,
                                      ServletResponse response, Object mappedValue) {
        HttpServletRequest req = (HttpServletRequest) request;
        // Ajax提交application/json数据的时候，会先发出Options请求
        // 这里要放行Options请求，不需要Shiro处理
        // 除了Options请求之外，所有请求都要被Shiro处理
        return req.getMethod().equals(RequestMethod.OPTIONS.name());
    }

    /**
     * 该方法用于处理所有应该被Shiro处理的请求
     */
    @Override
    protected boolean onAccessDenied(ServletRequest request,
                                     ServletResponse response) throws Exception {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse resp = (HttpServletResponse) response;
        log.debug("shiro拦截请求:{}", req.getRequestURI());

        // 获得token
        String token = getRequestToken(req);
        // 如果token不存在 || 失效 || 解析错误 直接返回
        if (StringUtils.isEmpty(token) || !JwtTokenUtil.verifierToken(token)) {
            return commonResp(req, resp, REnum.TOKEN_ERROR);
        }

        return executeLogin(request, response);
    }

    /**
     * 获取请求头里面的token
     */
    private String getRequestToken(HttpServletRequest httpRequest) {
        // System.out.println("httpRequest.getRequestURI() = " + httpRequest.getRequestURI());
        //从header中获取token
        String token = httpRequest.getHeader(Constant.ACCESS_TOKEN);

        // 如果header中不存在token，则从参数中获取token
        if (StringUtils.isEmpty(token)) token = httpRequest.getParameter(Constant.ACCESS_TOKEN);

        return token;
    }

    /**
     * @param token
     * @param e
     * @param request
     * @param response
     * @return boolean
     * token验证失败的回调
     * @date 2021/10/24 20:06
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token,
                                     AuthenticationException e, ServletRequest request, ServletResponse response) {
        return commonResp(request, response, REnum.TOKEN_DATABASE_ERROR);
    }

    private boolean commonResp(HttpServletRequest request, HttpServletResponse response, int code, String message) {
        try {
            request.setAttribute("error", Resp.error(code, message, null));
            request.getRequestDispatcher("/error/shiroFilter").forward(request, response);
        } catch (ServletException | IOException e) {
            log.error("shiroFilter commonResp转发请求发生错误:{}", e.getMessage());
        }
        return false;
    }

    private boolean commonResp(ServletRequest request, ServletResponse response, REnum rEnum) {

        return this.commonResp(((HttpServletRequest) request), ((HttpServletResponse) response), rEnum.getCode(), rEnum.getMessage());
    }

}
